﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace MotionControl.Modules.PLC.Business
{

    /// <summary>
    /// 西门子Modbus TCP协议通讯
    /// </summary>
    public class MODBUS_TCPIP
    {
        private object SyncObject = null;     

        public MODBUS_TCPIP()
        {
            this.SyncObject = new object();
        }

        private Socket socket = null;

        private static bool IsOnline(Socket c)
        {
            bool H1 = c.Poll(1500, SelectMode.SelectRead);
            bool H2 = c.Available == 0;
            bool H3 = c.Connected;
            bool H4 = !((H1) && (H2)) || (H3);
            return H4;
        }
        private string IP
        {
            get; set;
        }
        private int Port
        {
            get; set;
        }
        private int TimeOut
        {
            get; set;
        }

        /// <summary>
        /// 建立Modbus TCP连接
        /// </summary>
        /// <param name="ip">IP地址</param>
        /// <param name="port">端口</param>
        /// <param name="timeOut">毫秒</param>
        /// <returns>true,成功;false,失败</returns>
        public bool Connect(string ip, int port, int timeOut) //(string ip = "192.168.0.1",int port = 504,int timeOut = 1000)
        {
#if X_DEBUG
            Stopwatch sw = new Stopwatch();
            sw.Start();
#endif

            bool result = true;

            try
            {
                this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                this.socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, timeOut);
                this.socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, timeOut);
                IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);
                this.socket.Connect(ipEndPoint);

                result = true;

                this.IP = ip;
                this.Port = port;
                this.TimeOut = timeOut;
            }
            catch (System.Exception ex)
            {
                result = false;

                string msg = string.Format("Delta Modbus TCP Connect Failed. Exception Message:{0}.\n Stack Trace:{1}", ex.Message, ex.StackTrace);
                Debug.WriteLine(msg);
            }

#if X_DEBUG
            sw.Stop();
            PTC.CommonSrc.DebugWriteLine(string.Format("[DeltaModbusTCP.Connect]Elapsed {0}ms.", sw.ElapsedMilliseconds));
#endif

            return result;
        }

        public void Close()
        {
            try
            {
                if (this.socket != null)
                {
                    this.socket.Shutdown(SocketShutdown.Both);
                    this.socket.Close();
                    this.socket.Dispose();
                    this.socket = null;
                }
            }
            catch (System.Exception ex)
            {
                string msg = string.Format("Delta Modbus TCP Close Exception. Exception Message:{0}.\n Stack Trace:{1}", ex.Message, ex.StackTrace);
                Debug.WriteLine(msg);
            }
        }

        /// <summary>
        /// 获取一个整数的低16位值
        /// </summary>
        private static UInt16 LOWORD(int value)
        {
            return (UInt16)(value & 0xFFFF);
        }

        /// <summary>
        /// 获取一个整数的高16位值
        /// </summary>
        public static UInt16 HIWORD(int value)
        {
            return (UInt16)((value >> 16) & 0xFFFF);
        }

        /// <summary>
        /// 获取一个整数的低8位值
        /// </summary>
        public static byte LOWBYTE(int value)
        {
            return (byte)(value & 0xFF);
        }

        /// <summary>
        /// 获取一个整数的高8位值
        /// </summary>
        public static byte HIGHBYTE(int value)
        {
            return (byte)((value >> 8) & 0xFF);
        }

        /// <summary>
        /// 求一个有符号16位整数的补码
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private UInt32 GetComplementInt16(Int16 value)
        {
            if (value >= 0)
            {
                return (UInt32)value;
            }

            //求负数的补码
            //1.求原码: 最高位置1, 其余位为其正数的二进制表示
            //2.求反码: 最高位置1, 其余位为原码取反
            //3.求补码: 最高位置1, +1

            Int32 n = value;

            UInt32 OriginalCode = ((UInt32)(-n)) | (UInt32)0x8000;
            UInt32 AntiCode = ~OriginalCode | (UInt32)0x8000;
            UInt32 Complement = (AntiCode + 1) | (UInt32)0x8000;

            return Complement;
        }

        /// <summary>
        /// 写多个Word寄存器[D]
        /// Function code: 0x10 to write multiple word-device register values
        /// </summary>
        /// <param name="startAddressOfWord">要写入Word值的首地址.例如0x1000,0x1001</param>
        /// <param name="datas">要写入的Word值数组.该数组的长度为要写入的Word值的个数</param>
        /// <param name="result">异常返回码</param>
        /// <param name="stationNO">Modbus站号.默认为1</param>
        /// <returns>true,写入成功;false,写入失败</returns>
        public bool WriteMultiWordData(UInt16 startAddressOfWord, Int16[] datas, out int result, byte stationNO = 1)
        {
#if X_DEBUG
            Stopwatch sw = new Stopwatch();
            sw.Start();
#endif          
            lock (this.SyncObject)
            {
                try
                {
                    result = -1;
                    Debug.Assert(this.socket != null);

                    if (!MODBUS_TCPIP.IsOnline(this.socket))
                    {
                        if (!this.Connect(this.IP, this.Port, this.TimeOut))
                        {
                            string msg = string.Format("[LOGID1001]Write Multi Word Data ReConnect Failed");
                            Debug.WriteLine(msg);
                            return false;
                        }
                        else
                        {
                            string msg = string.Format("[LOGID1002]Write Multi Word Data ReConnect Success");
                            Debug.WriteLine(msg);
                        }
                    }
                    //
                    //写值
                    //
                    int datasLength = datas.Length;
                    int modbusDataLength = 7 + 2 * datasLength;//1 + 1 + 2 + 2 + 1 + 2 * datas.Length;

                    byte[] sendDatas = new byte[6 + modbusDataLength];

                    sendDatas[0] = 0;
                    sendDatas[1] = 0;
                    sendDatas[2] = 0;
                    sendDatas[3] = 0;


                    sendDatas[4] = HIGHBYTE(modbusDataLength);  //High byte
                    sendDatas[5] = LOWBYTE(modbusDataLength);   //Low byte

                    sendDatas[6] = stationNO;
                    sendDatas[7] = 0x10;

                    sendDatas[8] = HIGHBYTE(startAddressOfWord);
                    sendDatas[9] = LOWBYTE(startAddressOfWord);

                    sendDatas[10] = HIGHBYTE(datasLength);
                    sendDatas[11] = LOWBYTE(datasLength);

                    sendDatas[12] = (byte)(2 * datasLength);

                    //写入的数值
                    for (int i = 0; i < datasLength; ++i)
                    {
                        Int16 n = datas[i];
                        if (n >= 0)
                        {
                            sendDatas[13 + i * 2] = HIGHBYTE(datas[i]);
                            sendDatas[14 + i * 2] = LOWBYTE(datas[i]);
                        }
                        else
                        {
                            UInt32 complement = GetComplementInt16(n);

                            sendDatas[13 + i * 2] = HIGHBYTE((int)complement);
                            sendDatas[14 + i * 2] = LOWBYTE((int)complement);
                        }
                    }

                    byte[] receiveData1;
                    SendMes(sendDatas, out receiveData1);

                    if (receiveData1[1] == 0x90)
                    {
                        result = receiveData1[2];                      
                        return false;
                    }

#if X_DEBUG
                sw.Stop();
                PTC.CommonSrc.DebugWriteLine(string.Format("[DeltaModbusTCP.WriteMultiWordData]Elapsed {0}ms.", sw.ElapsedMilliseconds));
#endif

                    result = 0;                  
                    return true;
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }
        }

        /// <summary>
        /// 读多个Word寄存器[D]
        /// Function code: 0x10 to write multiple word-device register values
        /// </summary>
        /// <param name="startAddressOfWord">要读入Word值的首地址.例如0x1000,0x1001</param>
        /// <param name="lengthByWord">要读入的Word值个数</param>
        /// <param name="datas">保存要读入的Word值数组.该数组的长度为lengthByWord</param>
        /// <param name="result">异常返回码</param>
        /// <param name="stationNO">Modbus站号.默认为1</param>
        /// <returns>true,读入成功;false,读入失败</returns>
        public bool ReadMultiWordData(UInt16 startAddressOfWord, UInt16 lengthByWord, Int16[] datas, out int result, byte stationNO = 1)
        {
#if X_DEBUG
            Stopwatch sw = new Stopwatch();
            sw.Start();
#endif           
            if (socket == null)
            {
                result = -1;              
                return false;
            }
            if (socket.Available != 0)
            {
                byte[] receiveData1 = new byte[socket.Available];
                int ss = this.socket.Receive(receiveData1);
            }
            else
            {
                //Thread.Sleep(20);
                if (socket.Available != 0)
                {
                    byte[] receiveData1 = new byte[socket.Available];
                    int ss = this.socket.Receive(receiveData1);
                }
            }
            lock (this.SyncObject)
            {
                try
                {
                    result = -1;
                    if (!MODBUS_TCPIP.IsOnline(this.socket))
                    {
                        if (!this.Connect(this.IP, this.Port, this.TimeOut))
                        {
                            string msg = string.Format("[LOGID1003]Read Multi Word Data ReConnect Failed");
                            Debug.WriteLine(msg);                           
                            return false;
                        }
                        else
                        {
                            string msg = string.Format("[LOGID1004]Read Multi Word Data ReConnect Success");
                            Debug.WriteLine(msg);
                        }
                    }

                    //
                    //写值
                    //
                    byte[] data0 = new byte[]
                    {
                    0x00, 0x00, 0x00, 0x00,
                    0x00, 0x06,
                    stationNO,
                    0x03,
                    HIGHBYTE(startAddressOfWord), LOWBYTE(startAddressOfWord),
                    HIGHBYTE(lengthByWord), LOWBYTE(lengthByWord)
                    };

                    byte[] receiveData1;
                    SendMes(data0, out receiveData1);
                    if (receiveData1[1] == 0x83)
                    {
                        result = receiveData1[2];                     
                        return false;
                    }
                    if (receiveData1.Length == 6 && receiveData1[0] == 0 && receiveData1[1] == 0)
                    {
                        result = -1;                     
                        return false;
                    }
                    for (int i = 0; i < lengthByWord; ++i)
                    {
                        datas[i] = (Int16)(((receiveData1[3 + i * 2] << 8) | receiveData1[4 + i * 2]) & 0xFFFF);
                    }

#if X_DEBUG
                sw.Stop();
                PTC.CommonSrc.DebugWriteLine(string.Format("[DeltaModbusTCP.ReadMultiWordData]Elapsed {0}ms.", sw.ElapsedMilliseconds));
#endif

                    result = 0;                  
                    return true;
                }
                catch (Exception ex)
                {   
                    
                    throw ex;
                   
                }
            }


        }     
        public void SendMes(byte[] senddata, out byte[] receivedata)
        {
                int sendCount = 0;
                Stopwatch outTime = new Stopwatch();
                outTime.Start();
                Resend:
                outTime.Restart();
                int numberOfBytes = this.socket.Send(senddata);
                byte[] receiveData0;
                while (this.socket?.Available <= 0)
                {
                    if (outTime.Elapsed.TotalMilliseconds > 200)
                    {
                        sendCount++;
                        if (sendCount <= 1)
                        {
                            goto Resend;
                        }
                        else
                        {
                            receivedata = new byte[6];
                            break;
                        }
                    }
                    Thread.Sleep(10);

                }
                if (this.socket?.Available > 0)
                {
                    receiveData0 = new byte[6];
                    numberOfBytes = this.socket.Receive(receiveData0);
                    int n = (receiveData0[4] << 8) | receiveData0[5];
                    byte[] receiveData1 = new byte[n];
                    numberOfBytes = this.socket.Receive(receiveData1);
                    receivedata = receiveData1;
                }
                else
                {
                    receivedata = new byte[6];
                }          
        }
        public void SendMesAix(byte[] senddata, out byte[] receivedata, int sendCount = 0)
        {
            if (sendCount > 2)
            {
                receivedata = new byte[6];
                return;
            }
            sendCount++;
            Stopwatch outTime = new Stopwatch();
            outTime.Start();
            int numberOfBytes = this.socket.Send(senddata);
            byte[] receiveData0;
            while (this.socket.Available <= 0 && outTime.Elapsed.TotalSeconds < 1)
            {
                Thread.Sleep(10);
            }
            if (this.socket.Available > 0)
            {
                receiveData0 = new byte[6];
                numberOfBytes = this.socket.Receive(receiveData0);
                int n = (receiveData0[4] << 8) | receiveData0[5];
                byte[] receiveData1 = new byte[n];
                numberOfBytes = this.socket.Receive(receiveData1);
                receivedata = receiveData1;
            }
            else
            {
                SendMesAix(senddata, out receivedata, sendCount);
            }
        }
    }

}
